from bs4 import BeautifulSoup
from datetime import datetime
import re
import requests
import string


def url_checking(url: str):
    """Basic URL compliance/compatibility check."""
    if not isinstance(url, str):
        raise TypeError('the "url" argument must be a string.')

    if len(url) == 0:
        raise ValueError('the "url" argument contains no characters.')

    if len(set(url).difference(set(string.printable))) != 0:
        raise ValueError('the "url" argument contains illegal characters.')  # TD: edit restrictions @#$%&+=:;,?/


def url_handling(url: str):
    url_properties = dict()

    nickname = re.fullmatch(r"""@?(?P<contact>\w{5,32})""", url, flags=re.ASCII)
    if nickname:
        url_properties.update(nickname.groupdict())
        url_properties.update({'type': 'contact'})
        return url_properties

    def properties_handler(properties: dict):
        cleared_properties = dict()
        for i in properties:
            if properties.get(i):
                if i in ('preview', 'message'):
                    cleared_properties.update({'type': i})
                    if i == 'message':
                        cleared_properties.update({'id': properties.get(i)})
                    continue
                cleared_properties.update({i: properties.get(i)})
            if 'type' not in cleared_properties and len(cleared_properties) == 1:
                cleared_properties.update({'type': tuple(cleared_properties.keys())[0]})

        return cleared_properties

    http_url_re = r"""(https?://)?(t\.me/(?P<preview>s/)?)((add(?P<type>list|stickers))|(?P<contact>\w{5,32}))((/((?P<message>\d+)|(?P<id>\w+)))|(\?(q=(?P<query>[\w~`!^*()_|}{"><.'\[\]\\№+%]+)&?)?((?P<direction>before|after)=(?P<border>\d+))?))?"""
    http_url = re.fullmatch(http_url_re, url, flags=re.ASCII)
    if http_url:
        url_properties.update(properties_handler(http_url.groupdict()))
        return url_properties

    tg_url_re = r"""tg://((resolve\?domain=(?P<contact>\w{5,32})(&post=(?P<message>\d+))?)|(add(?P<type>list|stickers)\?(slug|set)=(?P<id>\w+)))"""
    tg_url = re.fullmatch(tg_url_re, url, flags=re.ASCII)
    if tg_url:
        url_properties.update(properties_handler(tg_url.groupdict()))
        return url_properties
    else:
        raise ValueError('Unsupported URL type expected.')


class API:
    def __init__(self, url: str, additional_checks=None):  # additional_checks=False for disable all ones
        if additional_checks:
            additional_checks(url)
        elif additional_checks is None:
            url_checking(url)
        self.url_info = url_handling(url)
        if self.url_info.get('type') == 'contact':
            self.contact = self.Contact(self.url_info.get('contact'))
        elif self.url_info.get('type') == 'message':
            self.message = self.Message(self.url_info.get('contact'), self.url_info.get('message'))
        elif self.url_info.get('type') == 'preview':
            self.preview = self.Preview(url)
        elif self.url_info.get('type') == 'stickers':
            self.stickers = self.Stickers(url)

    class Contact:
        def __init__(self, contact):
            base = 'https://t.me/'
            page_source = requests.get(base + contact).content
            contact_info_wrap = BeautifulSoup(page_source, 'lxml')
            preview = requests.get(base + 's/' + contact)
            preview_page_source = preview.content if preview.url != base + contact else None
            photo = dict()
            if preview_page_source:
                preview_channel_info_wrap = BeautifulSoup(preview_page_source, 'lxml').find('section', attrs={'class': 'tgme_right_column'})

                counter_wraps = preview_channel_info_wrap.find('div', attrs={'class': 'tgme_channel_info_counters'})
                counter_wraps = counter_wraps.find_all('div', attrs={'class': 'tgme_channel_info_counter'})

                counters = dict()
                for counter_wrap in counter_wraps:
                    c_type = counter_wrap.find('span', attrs={'class': 'counter_type'}).get_text()
                    c_value = counter_wrap.find('span', attrs={'class': 'counter_value'}).get_text()
                    counters.update({c_type: c_value})

                if len(counters) != 0:
                    self.counters = counters

                description = preview_channel_info_wrap.find('div', attrs={'class': 'tgme_channel_info_description'})
                if description:
                    self.description = description.get_text()  # wrap into text handler

                preview_channel_info_wrap = preview_channel_info_wrap.find('div', attrs={'class': 'tgme_channel_info_header'})

                photo_wrap = preview_channel_info_wrap.find('img')
                if photo_wrap:
                    photo.update({'160x160': photo_wrap.get('src')})

            contact_info_wrap = contact_info_wrap.find('div', attrs={'class': 'tgme_page'})

            photo_wrap = contact_info_wrap.find('img', attrs={'class': 'tgme_page_photo_image'})
            if photo_wrap:
                photo.update({'320x320': photo_wrap.get('src')})
            if len(photo) > 0:
                self.photo = photo

            title = contact_info_wrap.find('div', attrs={'class': 'tgme_page_title'})
            if title:
                self.title = title.get_text().strip()  # does that require wrapping into text handler?

                label = title.find('i').get('class')[0].rstrip('-icon')
                if label:
                    self.label = label

            extra = contact_info_wrap.find('div', attrs={'class': 'tgme_page_extra'})
            if extra:
                self.extra = extra.get_text()  # does that require wrapping into text handler?

            if not preview_page_source:
                self.description = contact_info_wrap.find('div', attrs={'class': 'tgme_page_description'}).get_text().strip()  # wrap into text handler

            #
            # Type definition for private/public(restricted -/channel or super-/group)
            #
            # action = contact_info_wrap.find('div', attrs={'class': 'tgme_page_action'}).get_text().strip()
            # if contact_info_wrap.find('div', attrs={'class': 'tgme_page_context_link_wrap'}):
            #     has_preview = True

    class Preview:
        def __init__(self, url: str):
            pass

    class Message:
        def __init__(self, url: str, message_wrap=None):
            self.source = None
            self.text = None
            self.media = None
            self.voice = None
            self.file = None
            self.poll = None
            self.link_preview = None
            self.metadata = None
            self.keyboard = None
            self.get_source(message_wrap)
            self.get_text(message_wrap)
            self.get_media(message_wrap)
            self.get_poll(message_wrap)
            self.get_link_preview(message_wrap)
            self.get_metadata(message_wrap)
            self.get_keyboard(message_wrap)

        def get_source(self, message):
            f_data = message.find('a', attrs={'class': 'tgme_widget_message_forwarded_from_name'})
            if f_data:
                f_title = f_data.get_text()
                f_link = f_data.get('href')
                self.source = {'title': f_title, 'link': f_link}

        def get_text(self, message):
            message_wrap_text = message.find('div', attrs={'class': 'tgme_widget_message_text'})
            if message_wrap_text:
                self.text = message_wrap_text

        def get_image(self, message, img_type=0):  # 0 - 'standart', 1 - group,  2 - 'preview', 3 - 'site-preview'
            if img_type not in (0, 1):
                class_prefix = 'link_preview' + ('_right' if img_type == 3 else '') + '_image'
                html_tag = 'i'
            else:
                class_prefix = 'tgme_widget_message_photo_wrap'
                html_tag = 'a'

            img_wrap = message.find(html_tag, attrs={'class': class_prefix}) if img_type != 1 else message
            img_style = img_wrap.get('style')
            img_link = re.search(r'background-image:url\(([\'"])(.+)\1\)', img_style)[2]
            media = {'type': 'image', 'source': img_link}
            if img_type == 0:
                self.media = (media,)
            else:
                return media

        def get_video(self, message, vid_type=0):  # 0 - standart, 1 - group, 2 - preview, 3 - 'round'
            class_prefix = 'tgme_widget_message_'
            time_class_prefix = 'message_'
            if vid_type in (0, 1):
                html_tag = 'a'
            else:
                html_tag = 'div'
                if vid_type == 2:
                    class_prefix = 'link_preview_'
                else:
                    class_prefix += 'round'
                    time_class_prefix = 'tgme_widget_' + time_class_prefix + 'round'

            vid_wrap = message.find(html_tag, attrs={'class': f'{class_prefix}video_player'}) if vid_type != 1 else message
            vid_thumb = vid_wrap.find('i', attrs={'class': f'{class_prefix}video_thumb'}).get('style')
            vid_thumb = re.search(r'background-image:url\(([\'"])(.+)\1\)', vid_thumb)[2]
            vid_source = vid_wrap.find('video', attrs={'class': f'{class_prefix}video'})
            vid_source = vid_source.get('src') if vid_source else None
            vid_duration = vid_wrap.find('time', attrs={'class': f'{time_class_prefix}video_duration'})
            vid_duration = vid_duration.get_text() if vid_duration else None
            media = {'type': 'video', 'thumbnail': vid_thumb, 'source': vid_source, 'duration': vid_duration}
            if vid_type == 0:
                self.media = (media,)
            else:
                return media

        def get_media(self, message):
            media_wraps = message.find('div', attrs={'class': 'tgme_widget_message_grouped_layer'})
            if media_wraps:
                media_wraps = media_wraps.contents
                media = []
                for media_wrap in media_wraps:
                    media.append(self.get_image(media_wrap, img_type=1) or self.get_video(media_wrap, vid_type=1))

                self.media = tuple(media)

            else:
                return self.get_image(message, img_type=0) or self.get_video(message, vid_type=0)

        def get_voice(self, message):
            voice_data = message.find('a', attrs={'class': 'tgme_widget_message_voice_player'})
            if voice_data:
                v_audio = voice_data.find('audio', attrs={'class': 'tgme_widget_message_voice'}).get('src')
                v_duration = voice_data.find('time', attrs={'class': 'tgme_widget_message_voice_duration'}).get_text()
                self.voice = {'link': v_audio, 'duration': v_duration}

        def get_file(self, message):
            doc_data = message.find('div', attrs={'class': 'tgme_widget_message_document'})
            if doc_data:
                d_title = doc_data.find('div', attrs={'class': 'tgme_widget_message_document_title'}).get_text()
                d_extra = doc_data.find('div', attrs={'class': 'tgme_widget_message_document_extra'}).get_text()
                self.file = {'title': d_title, 'extra': d_extra}

        def get_poll(self, message):
            poll_data = message.find('div', attrs={'class': 'tgme_widget_message_poll'})
            if poll_data:
                p_title = poll_data.find('div', attrs={'class': 'tgme_widget_message_poll_question'}).get_text()
                p_type = poll_data.find('div', attrs={'class': 'tgme_widget_message_poll_type'}).get_text()
                p_options = poll_data.find('a', attrs={'class': 'tgme_widget_message_poll_options'})
                p_options = p_options.find_all('div', attrs={'class': 'tgme_widget_message_poll_option'})
                p_opts = []
                for p_option in p_options:
                    opt_percent = p_option.find('div',
                                                attrs={'class': 'tgme_widget_message_poll_option_percent'}).get_text()
                    opt_text = p_option.find('div', attrs={'class': 'tgme_widget_message_poll_option_text'}).get_text()
                    p_opts.append({'percent': opt_percent, 'text': opt_text})

                self.poll = {'title': p_title, 'type': p_type, 'options': tuple(p_opts)}

        def get_link_preview(self, message):
            preview_data = message.find('a', attrs={'class': 'tgme_widget_message_link_preview'})
            s_link = preview_data.get('href')
            try:
                s_thumb = self.get_image(preview_data, img_type=3).get('source')
                s_name = preview_data.find('div', attrs={'class': 'link_preview_site_name'}).get_text()
                s_title = preview_data.find('div', attrs={'class': 'link_preview_title'}).get_text()
                s_description = preview_data.find('div', attrs={'class': 'link_preview_description'}).get_text()
                self.link_preview = {'url': s_link, 'site-preview': {'name': s_name, 'thumbnail': s_thumb, 'title': s_title, 'description': s_description}}
            except:
                self.link_preview = {'url': s_link, 'media': self.get_image(preview_data,
                                                                            img_type=2) or self.get_video(preview_data, vid_type=2)}

        def get_metadata(self, message):
            footer_data = message.find('div', attrs={'class': 'tgme_widget_message_footer'})
            f_voters = footer_data.find('span', attrs={'class': 'tgme_widget_message_voters'})
            f_voters = f_voters.get_text() if f_voters else None
            f_views = footer_data.find('span', attrs={'class': 'tgme_widget_message_views'})
            f_views = f_views.get_text() if f_views else None
            f_meta = footer_data.find('span', attrs={'class': 'tgme_widget_message_meta'})
            f_info = f_meta.find('a', attrs={'class': 'tgme_widget_message_date'})
            f_id = int(f_info.get('href').rsplit('/', 1)[-1])
            f_date = f_info.find('time', attrs={'class': 'time'}).get('datetime')
            f_date = datetime.strptime(f_date, '%Y-%m-%dT%H:%M:%S%z')
            f_edited = True if 'edited' in f_meta.contents[0] else False
            self.metadata = {'id': f_id, 'date': f_date, 'views': f_views, 'edited': f_edited, 'voters': f_voters}

        def get_keyboard(self, message):
            keyboard_data = message.find('div', attrs={'class': 'tgme_widget_message_inline_keyboard'})
            if keyboard_data:
                k_rows_data = keyboard_data.find_all('div', attrs={'class': 'tgme_widget_message_inline_row'})
                k_rows = []
                for k_row_data in k_rows_data:
                    k_row = []
                    buttons_data = k_row_data.find_all('a', attrs={'class': 'tgme_widget_message_inline_button'})
                    for button_data in buttons_data:
                        b_text = button_data.find('span', attrs={'class': 'tgme_widget_message_inline_button_text'}).get_text()
                        b_href = button_data.get('href')
                        k_row.append({'text': b_text, 'link': b_href})

                    k_rows.append(tuple(k_row))

                self.keyboard = tuple(k_rows)

    class Stickers:
        def __init__(self, url: str):
            pass

    class Lists:
        def __init__(self, url: str):
            pass
